﻿/* DrawHTML()
 * Drop-in replacement for DrawText() supporting a tiny subset of HTML.
 */
#include <windows.h>
#include <tchar.h>
#include <assert.h>

//use unprintable characters so it doesn't find copied html to convert

#define ENDFLAG   0x100
enum { tNONE, tB, tBR, tFONT, tI, tP, tSUB, tSUP, tU, tNUMTAGS };
struct
{
	char *mnemonic;
	short token, param, block;
} Tags[] = {
  { NULL,         tNONE, 0, 0},
  { _T("\x04"),   tFONT, 1, 0 },
  { _T("\x05"),   tBR,   0, 1 },
  /*{ _T("b"),      tB,    0, 0},
  { _T("br"),     tBR,   0, 1},
  { _T("em"),     tI,    0, 0},
  { _T("font"),   tFONT, 1, 0},
  { _T("i"),      tI,    0, 0},
  { _T("p"),      tP,    0, 1},
  { _T("strong"), tB,    0, 0},
  { _T("sub"),    tSUB,  0, 0},
  { _T("sup"),    tSUP,  0, 0},
  { _T("u"),      tU,    0, 0},*/
};

static int GetToken(LPCTSTR *String, int *Size, int *TokenLength, BOOL *WhiteSpace)
{
	LPCTSTR Start, EndToken;
	int Length, EntryWhiteSpace, Index, IsEndTag;

	assert(String != NULL && *String != NULL);
	assert(Size != NULL);
	Start = *String;

	/* check for leading white space, then skip it */
	if (WhiteSpace != NULL)
	{
		EntryWhiteSpace = *WhiteSpace;
		*WhiteSpace = EntryWhiteSpace || _istspace(*Start);
	}
	else
	{
		EntryWhiteSpace = FALSE;
	} /* if */
	while (*Size > 0 && _istspace(*Start))
	{
		Start++;
		*Size -= 1;
	} /* if */
	if (*Size <= 0)
		return -1;  /* no printable text left */

	EndToken = Start;
	Length = 0;
	IsEndTag = 0;
	if (*EndToken == _T('\x01'))
	{
		/* might be a HTML tag, check */
		EndToken++;
		Length++;
		if (Length < *Size && *EndToken == _T('\x03'))
		{
			IsEndTag = ENDFLAG;
			EndToken++;
			Length++;
		} /* if */
		while (Length < *Size && !_istspace(*EndToken)
			&& *EndToken != _T('\x01') && *EndToken != _T('\x02'))
		{
			EndToken++;
			Length++;
		} /* while */
		for (Index = sizeof Tags / sizeof Tags[0] - 1; Index > 0; Index--)
			if (!_tcsnicmp(Start + (IsEndTag ? 2 : 1), Tags[Index].mnemonic,
				_tcslen(Tags[Index].mnemonic)))
				break;
		if (Index > 0)
		{
			/* so it is a tag, see whether to accept parameters */
			if (Tags[Index].param && !IsEndTag)
			{
				while (Length < *Size
					&& *EndToken != _T('\x01') && *EndToken != _T('\x02'))
				{
					EndToken++;
					Length++;
				} /* while */
			}
			else if (*EndToken != _T('\x02'))
			{
				/* no parameters, then '>' must follow the tag */
				Index = 0;
			} /* if */
			if (WhiteSpace != NULL && Tags[Index].block)
				*WhiteSpace = FALSE;
		} /* if */
		if (*EndToken == _T('\x02'))
		{
			EndToken++;
			Length++;
		} /* if */
		/* skip trailing white space in some circumstances */
		if (Index > 0 && (Tags[Index].block || EntryWhiteSpace))
		{
			while (Length < *Size && _istspace(*EndToken))
			{
				EndToken++;
				Length++;
			} /* while */
		} /* if */

	}
	else
	{
		/* normal word (no tag) */
		Index = 0;
		while (Length < *Size && !_istspace(*EndToken) && *EndToken != _T('\x01'))
		{
			EndToken++;
			Length++;
		} /* while */
	} /* if */

	if (TokenLength != NULL)
		*TokenLength = Length;
	*Size -= Length;
	*String = Start;
	return Tags[Index].token | IsEndTag;
}

static int HexDigit(TCHAR ch)
{
	if (ch >= _T('0') && ch <= _T('9'))
		return ch - _T('0');
	if (ch >= _T('A') && ch <= _T('F'))
		return ch - _T('A') + 10;
	if (ch >= _T('a') && ch <= _T('f'))
		return ch - _T('a') + 10;
	return 0;
}

static COLORREF ParseColor(LPCTSTR String)
{
	int Red, Green, Blue;

	if (*String == _T('\'') || *String == _T('"'))
		String++;
	if (*String == _T('#'))
		String++;
	Red = (HexDigit(String[0]) << 4) | HexDigit(String[1]);
	Green = (HexDigit(String[2]) << 4) | HexDigit(String[3]);
	Blue = (HexDigit(String[4]) << 4) | HexDigit(String[5]);
	return RGB(Red, Green, Blue);
}

#define STACKSIZE   8
static COLORREF stack[STACKSIZE];
static int stacktop;

static BOOL PushColor(HDC hdc, COLORREF clr)
{
	if (stacktop < STACKSIZE)
		stack[stacktop++] = GetTextColor(hdc);
	SetTextColor(hdc, clr);
	return TRUE;
}

static BOOL PopColor(HDC hdc)
{
	COLORREF clr;
	BOOL okay = (stacktop > 0);

	if (okay)
		clr = stack[--stacktop];
	else
		clr = stack[0];
	SetTextColor(hdc, clr);
	return okay;
}


#define FV_BOLD        0x01
#define FV_ITALIC      (FV_BOLD << 1)
#define FV_UNDERLINE   (FV_ITALIC << 1)
#define FV_SUPERSCRIPT (FV_UNDERLINE << 1)
#define FV_SUBSCRIPT   (FV_SUPERSCRIPT << 1)
#define FV_NUMBER      (FV_SUBSCRIPT << 1)

static HFONT GetFontVariant(HDC hdc, HFONT hfontSource, int Styles)
{
	LOGFONT logFont = { 0 };

	SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT));
	if (!GetObject(hfontSource, sizeof logFont, &logFont))
		return NULL;

	/* set parameters, create new font */
	logFont.lfWeight = (Styles & FV_BOLD) ? FW_BOLD : FW_NORMAL;
	logFont.lfItalic = (BYTE)(Styles & FV_ITALIC) != 0;
	logFont.lfUnderline = (BYTE)(Styles & FV_UNDERLINE) != 0;
	if (Styles & (FV_SUPERSCRIPT | FV_SUBSCRIPT))
		logFont.lfHeight = logFont.lfHeight * 7 / 10;
	return CreateFontIndirect(&logFont);
}

#if defined __cplusplus
extern "C"
#endif
int __stdcall DrawHTML(
	HDC     hdc,        // handle of device context
	LPCTSTR lpString,   // address of string to draw
	int     nCount,     // string length, in characters
	LPRECT  lpRect,     // address of structure with formatting dimensions
	UINT    uFormat     // text-drawing flags
)
{
	LPCTSTR Start;
	int Left, Top, MaxWidth, MinWidth, Height, MaxHeight;
	int SavedDC;
	int Tag, TokenLength;
	HFONT hfontBase, hfontSpecial[FV_NUMBER];
	int Styles, CurStyles;
	SIZE size;
	int Index, LineHeight;
	POINT CurPos;
	int WidthOfSPace, XPos;
	BOOL WhiteSpace;
	RECT rc;

	if (hdc == NULL || lpString == NULL)
		return 0;
	if (nCount < 0)
		nCount = _tcslen(lpString);

	MaxHeight = INT_MAX;

	if (lpRect != NULL)
	{
		Left = lpRect->left;
		Top = lpRect->top;
		MaxWidth = lpRect->right - lpRect->left;
		MaxHeight = lpRect->bottom - lpRect->top;
	}
	else
	{
		GetCurrentPositionEx(hdc, &CurPos);
		Left = CurPos.x;
		Top = CurPos.y;
		MaxWidth = GetDeviceCaps(hdc, HORZRES) - Left;
	} /* if */
	if (MaxWidth < 0)
		MaxWidth = 0;

	/* toggle flags we do not support */
	uFormat &= ~(DT_CENTER | DT_RIGHT | DT_TABSTOP);
	uFormat |= (DT_LEFT | DT_NOPREFIX);

	/* get the "default" font from the DC */
	SavedDC = SaveDC(hdc);
	hfontBase = SelectObject(hdc, (HFONT)GetStockObject(SYSTEM_FONT));
	SelectObject(hdc, hfontBase);
	/* clear the other fonts, they are created "on demand" */
	for (Index = 0; Index < FV_NUMBER; Index++)
		hfontSpecial[Index] = NULL;
	hfontSpecial[0] = hfontBase;
	Styles = 0; /* assume the active font is normal weight, roman, non-underlined */

	/* get font height (use characters with ascender and descender);
	 * we make the assumption here that changing the font style will
	 * not change the font height
	 */
	GetTextExtentPoint32(hdc, _T("Åy"), 2, &size);
	LineHeight = size.cy;

	/* run through the string, word for word */
	XPos = 0;
	MinWidth = 0;
	stacktop = 0;
	CurStyles = -1; /* force a select of the proper style */
	Height = 0;
	WhiteSpace = FALSE;

	Start = lpString;
	for (;; )
	{
		Tag = GetToken(&Start, &nCount, &TokenLength, &WhiteSpace);
		if (Tag < 0)
			break;
		switch (Tag & ~ENDFLAG)
		{
		case tP:
			if ((Tag & ENDFLAG) == 0 && (uFormat & DT_SINGLELINE) == 0)
			{
				if (Start != lpString)
					Height += 3 * LineHeight / 2;
				XPos = 0;
			} /* if */
			break;
		case tBR:
			if ((Tag & ENDFLAG) == 0 && (uFormat & DT_SINGLELINE) == 0)
			{
				Height += LineHeight;
				XPos = 0;
			} /* if */
			break;
		case tB:
			Styles = (Tag & ENDFLAG) ? Styles & ~FV_BOLD : Styles | FV_BOLD;
			break;
		case tI:
			Styles = (Tag & ENDFLAG) ? Styles & ~FV_ITALIC : Styles | FV_ITALIC;
			break;
		case tU:
			Styles = (Tag & ENDFLAG) ? Styles & ~FV_UNDERLINE : Styles | FV_UNDERLINE;
			break;
		case tSUB:
			Styles = (Tag & ENDFLAG) ? Styles & ~FV_SUBSCRIPT : Styles | FV_SUBSCRIPT;
			break;
		case tSUP:
			Styles = (Tag & ENDFLAG) ? Styles & ~FV_SUPERSCRIPT : Styles | FV_SUPERSCRIPT;
			break;
		case tFONT:
			if ((Tag & ENDFLAG) == 0)
			{
				if (_tcsnicmp(Start + 3, _T("color="), 6) == 0)
					PushColor(hdc, ParseColor(Start + 9));
			}
			else
			{
				PopColor(hdc);
			} /* if */
			break;
		default:
			if (Tag == (tNONE | ENDFLAG))
				break;
			if (CurStyles != Styles)
			{
				if (hfontSpecial[Styles] == NULL)
					hfontSpecial[Styles] = GetFontVariant(hdc, hfontBase, Styles);
				CurStyles = Styles;
				SelectObject(hdc, hfontSpecial[Styles]);
				/* get the width of a space character (for word spacing) */
				GetTextExtentPoint32(hdc, _T(" "), 1, &size);
				WidthOfSPace = size.cx;
			} /* if */
			/* check word length, check whether to wrap around */
			GetTextExtentPoint32(hdc, Start, TokenLength, &size);
			if (size.cx > MaxWidth)
				MaxWidth = size.cx;   /* must increase width: long non-breakable word */
			if (WhiteSpace)
				XPos += WidthOfSPace;
			if (XPos + size.cx > MaxWidth && WhiteSpace)
			{
				if ((uFormat & DT_WORDBREAK) != 0)
				{
					/* word wrap */
					Height += LineHeight;
					XPos = 0;
				}
				else
				{
					/* no word wrap, must increase the width */
					MaxWidth = XPos + size.cx;
				} /* if */
			} /* if */
			/* output text (unless DT_CALCRECT is set) */
			if ((uFormat & DT_CALCRECT) == 0)
			{
				/* handle negative heights, too (suggestion of "Sims")  */
				if (Top < 0)
					SetRect(&rc, Left + XPos, Top - Height,
						Left + MaxWidth, Top - (Height + LineHeight));
				else
					SetRect(&rc, Left + XPos, Top + Height,
						Left + MaxWidth, Top + Height + LineHeight);

				/* reposition subscript text to align below the baseline */
				DrawText(hdc, Start, TokenLength, &rc,
					uFormat | ((Styles & FV_SUBSCRIPT) ? DT_BOTTOM | DT_SINGLELINE : 0));

				/* for the underline style, the spaces between words should be
				 * underlined as well
				 */
				if (WhiteSpace && (Styles & FV_UNDERLINE) && XPos >= WidthOfSPace)
				{
					if (Top < 0)
						SetRect(&rc, Left + XPos - WidthOfSPace, Top - Height,
							Left + XPos, Top - (Height + LineHeight));
					else
						SetRect(&rc, Left + XPos - WidthOfSPace, Top + Height,
							Left + XPos, Top + Height + LineHeight);
					DrawText(hdc, " ", 1, &rc, uFormat);
				} /* if */
			} /* if */
			/* update current position */
			XPos += size.cx;
			if (XPos > MinWidth)
				MinWidth = XPos;
			WhiteSpace = FALSE;
		} /* if */

		if ((Height + LineHeight) >= MaxHeight)
			break;

		Start += TokenLength;
	} /* for */

	RestoreDC(hdc, SavedDC);
	for (Index = 1; Index < FV_NUMBER; Index++) /* do not erase hfontSpecial[0] */
		if (hfontSpecial[Index] != NULL)
			DeleteObject(hfontSpecial[Index]);

	/* store width and height back into the lpRect structure */
	if ((uFormat & DT_CALCRECT) != 0 && lpRect != NULL)
	{
		lpRect->right = lpRect->left + MinWidth;
		if (lpRect->top < 0)
			lpRect->bottom = lpRect->top - (Height + LineHeight);
		else
			lpRect->bottom = lpRect->top + Height + LineHeight;
	} /* if */

	return Height;
}
